﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Collections.ObjectModel;

namespace Zx
{
    public class ExpMaker
    {
        /// <summary>
        /// 将委托转换为表达式
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="exp"></param>
        /// <returns></returns>
	    public static Expression<Func<T, bool>> GetBool<T>(Expression<Func<T, bool>> exp)
	    {
		    return exp;
	    }
    }

	public static class LambdaExt
	{
        /// <summary>
        /// 获取实例属性的表达式
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TR"></typeparam>
        /// <param name="name">属性</param>
        /// <example>GetF("property") = it.property</example>
        /// <returns></returns>
		public static Func<T, TR> GetF<T, TR>(string name)
		{
			// target: (object)((({TargetType})instance).{Property})

			// preparing parameter, object type
			ParameterExpression instance = Expression.Parameter(
				typeof(object), "instance");
			var propertyInfo = typeof(T).GetProperty(name);
			// ({TargetType})instance
			Expression instanceCast = Expression.Convert(
				instance, typeof(T));

			// (({TargetType})instance).{Property}
			Expression propertyAccess = Expression.Property(
				instanceCast, propertyInfo);

			// (object)((({TargetType})instance).{Property})
			UnaryExpression castPropertyValue = Expression.Convert(
				propertyAccess, typeof(TR));

			// Lambda expression
			Expression<Func<T, TR>> lambda =
				Expression.Lambda<Func<T, TR>>(
					castPropertyValue, instance);

			return lambda.Compile();
		}

        /// <summary>
        /// 为当前表达式添加一个And条件来组成一个新的表达式
        /// </summary>
        /// <typeparam name="T">类型参数</typeparam>
        /// <param name="where">原有的表达式</param>
        /// <param name="and">表达式</param>
        /// <returns>新表达式</returns>
		public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> where, Expression<Func<T, bool>> and)
		{
			var newBody = MakeNew(and.Body, where.Parameters, and.Parameters).Reduce();
			Expression<Func<T, bool>> newExp = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(where.Body, newBody), where.Parameters);
			return newExp;
		}

        /// <summary>
        /// 为当前表达式添加一个Or条件来组成一个新的表达式
        /// </summary>
        /// <typeparam name="T">类型参数</typeparam>
        /// <param name="where">原有的表达式</param>
        /// <param name="and">表达式</param>
        /// <returns>新表达式</returns>
		public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> where, Expression<Func<T, bool>> and)
		{
			var newBody = MakeNew(and.Body, where.Parameters, and.Parameters).Reduce();
			Expression<Func<T, bool>> newExp = Expression.Lambda<Func<T, bool>>(Expression.OrElse(where.Body, newBody), where.Parameters);
			return newExp;
		}

        private static Expression MakeNew(Expression old, ReadOnlyCollection<ParameterExpression> newp, ReadOnlyCollection<ParameterExpression> oldp = null)
		{
			return new MakeNewExpVisitor(newp, oldp).Visit(old);
		}
	}

	internal class MakeNewExpVisitor : ExpressionVisitor
	{
		private Dictionary<ParameterExpression, ParameterExpression> _paramDic;

		public MakeNewExpVisitor(ReadOnlyCollection<ParameterExpression> newp, ReadOnlyCollection<ParameterExpression> oldp)
		{
			if (newp == null || oldp == null)
				throw new ArgumentNullException("'newp' or 'oldp' should not be null");
			if (newp.Count != oldp.Count)
				throw new ArgumentException("The count of the two collection should be equal");

			var newOrderP = newp.OrderBy(i => i.Name).ToList();
			var oldOrderP = oldp.OrderBy(i => i.Name).ToList();

			_paramDic = new Dictionary<ParameterExpression, ParameterExpression>();
			for (var i = 0; i < oldp.Count; i++)
			{
				_paramDic.Add(oldOrderP[i], newOrderP[i]);
			}
		}

		protected override Expression VisitParameter(ParameterExpression node)
		{
			if (ShouldReplace(node))
			{
				return _paramDic[node];
			}
			return base.VisitParameter(node);
		}


		/// <summary>
		/// 判断当前表达式是否需要被替换
		/// </summary>
		/// <param name="old">参数表达式</param>
		/// <returns></returns>
		private bool ShouldReplace(ParameterExpression old)
		{
			if (old == null||_paramDic == null)
				return false;
			if (_paramDic.ContainsKey(old))
				return true;
			return false;
		}
	}
}
